// types.cpp : Defines basic types (vectors, ray, color), and operations on it.
//

#include <cmath>
#include <iostream>
#include <iomanip>
#include <string>
#include <sstream>

#include "types.hpp"

using namespace std;

Vector3::Vector3(double x_, double y_, double z_)
{
	x = x_;
	y = y_;
	z = z_;
}

ostream& operator<<(ostream& os, const Vector3& dt)
{
	os << "(" << dt.x << ", " << dt.y << ", " << dt.z << ")";
	return os;
}

Vector3 Vector3::operator+(const Vector3& arg) const
{
	return Vector3(x + arg.x, y + arg.y, z + arg.z);
}

Vector3 Vector3::operator-(const Vector3& arg) const
{
	return Vector3(x - arg.x, y - arg.y, z - arg.z);
}

Vector3 Vector3::operator*(const Vector3& arg) const
{
	return Vector3(y * arg.z - z * arg.y, z * arg.x - x * arg.z, x * arg.y - y * arg.x);
}

Vector3 Vector3::operator*(double arg) const
{
	return Vector3(x * arg, y * arg, z * arg);
}

double dot(const Vector3& v1, const Vector3& v2)
{
	return v1.x * v2.x + v1.y * v2.y + v1.z * v2.z;
}

double mod(const Vector3& v)
{
	return sqrt(v.x * v.x + v.y * v.y + v.z * v.z);
}

Vector3 normalize(const Vector3& v)
{
	return v * (1.0 / mod(v));
}

Matrix3::Matrix3(Vector3 v1, Vector3 v2, Vector3 v3)
{
	d[0][0] = v1.x; d[0][1] = v1.y; d[0][2] = v1.z;
	d[1][0] = v2.x; d[1][1] = v2.y; d[1][2] = v2.z;
	d[2][0] = v3.x; d[2][1] = v3.y; d[2][2] = v3.z;
}

Vector3 Matrix3::operator*(const Vector3& v) const
{
	return Vector3(
		d[0][0] * v.x + d[1][0] * v.y + d[2][0] * v.z,
		d[0][1] * v.x + d[1][1] * v.y + d[2][1] * v.z,
		d[0][2] * v.x + d[1][2] * v.y + d[2][2] * v.z);
}

Matrix3 Matrix3::operator*(const Matrix3& v) const
{
	Matrix3 r;
	for(int i = 0; i < 3; i++)
	{
		for(int j = 0; j < 3; j++)
		{
			r.d[i][j] = 0.0;
			for(int k = 0; k < 3; k++)
				r.d[i][j] += d[i][k] * v.d[k][j];
		}
	}
	return r;
}

double determinant(const Matrix3& m)
{
	return m.d[0][0] * m.d[1][1] * m.d[2][2] +
		m.d[1][0] * m.d[2][1] * m.d[0][2] + 
		m.d[2][0] * m.d[0][1] * m.d[1][2] -
		m.d[0][2] * m.d[1][1] * m.d[2][0] -
		m.d[1][2] * m.d[2][1] * m.d[0][0] - 
		m.d[2][2] * m.d[0][1] * m.d[1][0];
}

ostream& operator<<(ostream& os, const Matrix3& dt)
{
	os << endl;
	os << "|" << dt.d[0][0] << "   " << dt.d[0][1] << "   " << dt.d[0][2] << "|" << endl;
	os << "|" << dt.d[1][0] << "   " << dt.d[1][1] << "   " << dt.d[1][2] << "|" << endl;
	os << "|" << dt.d[2][0] << "   " << dt.d[2][1] << "   " << dt.d[2][2] << "|" << endl;
	return os;
}

Matrix3 inverse(const Matrix3& m)
{
	Matrix3 r;

	double det = determinant(m);
	r.d[0][0] =  (m.d[1][1] * m.d[2][2] - m.d[1][2] * m.d[2][1]) / det;
	r.d[0][1] = -(m.d[0][1] * m.d[2][2] - m.d[0][2] * m.d[2][1]) / det;
	r.d[0][2] =  (m.d[0][1] * m.d[1][2] - m.d[0][2] * m.d[1][1]) / det;
	r.d[1][0] = -(m.d[1][0] * m.d[2][2] - m.d[1][2] * m.d[2][0]) / det;
	r.d[1][1] =  (m.d[0][0] * m.d[2][2] - m.d[0][2] * m.d[2][0]) / det;
	r.d[1][2] = -(m.d[0][0] * m.d[1][2] - m.d[0][2] * m.d[1][0]) / det;
	r.d[2][0] =  (m.d[1][0] * m.d[2][1] - m.d[1][1] * m.d[2][0]) / det;
	r.d[2][1] = -(m.d[0][0] * m.d[2][1] - m.d[0][1] * m.d[2][0]) / det;
	r.d[2][2] =  (m.d[0][0] * m.d[1][1] - m.d[0][1] * m.d[1][0]) / det;

	return r;
}


Ray::Ray(Vector3 origin, Vector3 direction)
{
	origin_ = origin;
	direction_ = normalize(direction);
}

ostream& operator<<(ostream& os, const Ray& dt)
{
	os << "ray" << dt.origin_ << "->" << dt.direction_;
	return os;
}

Vector3 Ray::getOrigin() const
{
	return origin_;
}

Vector3 Ray::getDirection() const
{
	return direction_;
}

Color::Color(double r, double g, double b)
{
	r_ = r;
	g_ = g;
	b_ = b;
}

Color Color::operator*(const Color& c) const
{
	return Color(r_ * c.r_, g_ * c.g_, b_ * c.b_);
}

Color Color::operator*(double k) const
{
	return Color(r_ * k, g_ * k, b_ * k);
}

Color Color::operator+(const Color& c) const
{
	return Color(r_ + c.r_, g_ + c.g_, b_ + c.b_);
}

double Color::getR() const
{
	return r_;
}

double Color::getG() const
{
	return g_;
}

double Color::getB() const
{
	return b_;
}

ostream& operator<<(ostream& os, const Color& dt)
{
	os << "c" << "R" << dt.getR() << "G" << dt.getG() << "B" << dt.getB();
	return os;
}

BBox::BBox(const BBox & box)
: min_(box.min_), max_(box.max_)
{
}

BBox::BBox(Vector3 v)
	: min_(v), max_(v)
{
}

BBox BBox::operator+(const Vector3& v)
{
	BBox result(*this);
	result.min_ = Vector3(v.x < min_.x ? v.x : min_.x, v.x < min_.x ? v.x : min_.x, v.x < min_.x ? v.x : min_.x);
	result.max_ = Vector3(v.x > max_.x ? v.x : max_.x, v.x > max_.x ? v.x : max_.x, v.x > max_.x ? v.x : max_.x);
	return result;
}
